package com.plugin.storage.storage.aliyun;

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.comm.Protocol;
import com.aliyun.oss.common.utils.CRC64;
import com.aliyun.oss.common.utils.IOUtils;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.internal.OSSUtils;
import com.aliyun.oss.model.*;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.plugin.storage.aliyun.AliyunSTS;
import com.plugin.storage.base.AuthToken;
import com.plugin.storage.base.SdkCache;
import com.plugin.storage.base.SdkContext;
import com.plugin.storage.base.StorageException;
import com.plugin.storage.storage.StorageUtils;
import com.plugin.storage.storage.model.UploadFile;
import com.plugin.storage.storage.model.UploadFileReq;
import com.plugin.storage.storage.spi.UploadProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ThreadLocalRandom;
import java.util.zip.CheckedInputStream;

import static com.plugin.storage.storage.StorageUtils.getEndpoint;


public class AliyunOSS implements UploadProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(AliyunOSS.class);

    private static final String POLICY_TEMPLATE = "{\n" +
            "  \"Statement\": [\n" +
            "    {\n" +
            "      \"Action\": [\n" +
            "        \"oss:%s\"\n" +
            "      ],\n" +
            "      \"Effect\": \"Allow\",\n" +
            "      \"Resource\": [\n" +
            "        \"acs:oss:*:*:%s\"\n" +
            "      ]\n" +
            "    }\n" +
            "  ],\n" +
            "  \"Version\": \"1\"\n" +
            "}";

    @Override
    public UploadFile upload(SdkContext context, UploadFileReq uploadReq) {
        AuthToken token = auth(context, uploadReq);
        OSS ossClient = getOss(context, token);

        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setServerSideEncryption(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
        metadata.setLastModified(uploadReq.getMeta().getLastModified());
        if (!Strings.isNullOrEmpty(uploadReq.getMeta().getContentType())) {
            metadata.setContentType(uploadReq.getMeta().getContentType());
        }
        if (!Strings.isNullOrEmpty(uploadReq.getMeta().getContentEncoding())) {
            metadata.setContentEncoding(uploadReq.getMeta().getContentEncoding());
        }

        metadata.setUserMetadata(ImmutableMap.of("origin-filename", uploadReq.getMeta().getOriginFileName()));

        UploadFileRequest uploadFileRequest = new UploadFileRequest(context.getBucket(), uploadReq.getKey());
        uploadFileRequest.setUploadFile(uploadReq.getLocalPath());
        // Sets the concurrent upload task number to 5.
        uploadFileRequest.setTaskNum(5);
        // Sets the part size to 1MB.
        uploadFileRequest.setPartSize(1024 * 1024);
        // Enables the checkpoint file. By default it's off.
        uploadFileRequest.setEnableCheckpoint(true);

        uploadFileRequest.setObjectMetadata(metadata);

        try {
            UploadFileResult uploadResult = ossClient.uploadFile(uploadFileRequest);
            CompleteMultipartUploadResult multipartUploadResult =
                    uploadResult.getMultipartUploadResult();
            return new UploadFile(context.getAccessEndpoint(), multipartUploadResult.getKey())
                    .setOriginFileName(uploadReq.getMeta().getOriginFileName())
                    .setAccessUrl(url(context, uploadReq));
        } catch (Throwable e) {
            LOGGER.error(e.getMessage(), e);
            throw new StorageException("上传失败:" + e.getMessage(), e);
        } finally {
            ossClient.shutdown();
        }
    }

    @Override
    public UploadFile download(SdkContext context, UploadFileReq uploadReq) {
        if (Strings.isNullOrEmpty(uploadReq.getLocalPath())) {
            throw new StorageException("下载失败,无目标路径");
        }
        AuthToken token = auth(context, uploadReq, true);
        OSS ossClient = getOss(context, token);

        DownloadFileRequest downloadFileRequest = new DownloadFileRequest(context.getBucket(), uploadReq.getKey());
        // Sets the local file to download to
        downloadFileRequest.setDownloadFile(uploadReq.getLocalPath());
        // Sets the concurrent task thread count 5. By default it's 1.
        downloadFileRequest.setTaskNum(5);
        // Sets the part size, by default it's 100K.
        downloadFileRequest.setPartSize(1024 * 1024);
        // Enable checkpoint. By default it's false.
        downloadFileRequest.setEnableCheckpoint(true);

        try {
            ObjectMetadata metadata = null;
            Long crc64 = 0L;
            if (uploadReq.getLocalFile().length() > 0) {
                LOGGER.debug("{} is exists.", uploadReq.getLocalPath());
                crc64 = getCRC64(uploadReq.getLocalFile());
                metadata = ossClient.getObjectMetadata(context.getBucket(), uploadReq.getKey());
            }
            if (metadata == null ||
                    !crc64.equals(
                            Long.valueOf(metadata.getRawMetadata().get(OSSHeaders.OSS_HASH_CRC64_ECMA).toString()))) {
                DownloadFileResult downloadResult = ossClient.downloadFile(downloadFileRequest);
                metadata = downloadResult.getObjectMetadata();
            }
            OSSUtils.checkChecksum(getCRC64(uploadReq.getLocalFile()),
                    Long.valueOf(metadata.getRawMetadata().get(OSSHeaders.OSS_HASH_CRC64_ECMA).toString()),
                    metadata.getRequestId());
            return new UploadFile(context.getAccessEndpoint(), uploadReq.getKey(), uploadReq.getLocalFile())
                    .setOriginFileName(metadata.getUserMetadata().get("origin-filename"))
                    .setAccessUrl(url(context, uploadReq));
        } catch (Throwable e) {
            LOGGER.error(e.getMessage(), e);
            throw new StorageException("下载失败:" + e.getMessage(), e);
        } finally {
            ossClient.shutdown();
        }
    }

    protected Long getCRC64(File file) {
        try (InputStream inputStream = new CheckedInputStream(Files.asByteSource(file).openBufferedStream(), new CRC64())) {
            ByteStreams.toByteArray(inputStream);
            return IOUtils.getCRCValue(inputStream);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
        }
        return 0L;
    }


    protected OSS getOss(SdkContext context, AuthToken token) {
        ClientBuilderConfiguration configuration = new ClientBuilderConfiguration();
        configuration.setProtocol(Protocol.HTTPS);
        return new OSSClientBuilder().build(context.getUploadEndpoint(), token.getAccessKeyId(), token.getAccessKeySecret(), token.getSecurityToken(), configuration);
    }

    @Override
    public UploadFile.Auth auth(SdkContext sdkContext, UploadFileReq uploadReq) {
        return auth(sdkContext, uploadReq, false);
    }

    private UploadFile.Auth auth(SdkContext sdkContext, UploadFileReq uploadReq, boolean download) {
        String resource = sdkContext.getBucket() + "/" + uploadReq.getKey();
        if (Strings.isNullOrEmpty(uploadReq.getKey())) {
            resource = sdkContext.getBucket() + "/" + uploadReq.getTargetDir() + "/*";
        }
        String action = download ? "GetObject" : "PutObject";
        String policyText = String.format(POLICY_TEMPLATE, action, resource);

        return new UploadFile.Auth(AliyunSTS.authToken(sdkContext, policyText))
                .setEndpoint(sdkContext.getUploadEndpoint())
                .setBucket(sdkContext.getBucket())
                .setRegion(sdkContext.getRegion())
                .setTargetDir(uploadReq.getTargetDir())
                .setKey(uploadReq.getKey());
    }

    @Override
    public String url(SdkContext sdkContext, UploadFileReq uploadReq) {
        if (Strings.isNullOrEmpty(uploadReq.getKey())) {
            return "";
        }
        String sourceUrl = sdkContext.getAccessEndpoint() + "/" + uploadReq.getKey();

        if (Strings.isNullOrEmpty(sdkContext.getCdnKey())) {
            return sourceUrl;
        }
        String url = SdkCache.getUrl(sourceUrl);
        if (!Strings.isNullOrEmpty(url)) {
            return url;
        }
        String timestamp = String.valueOf(StorageUtils.getTimestamp(sdkContext.getTimeout()));
        int rand = ThreadLocalRandom.current().nextInt(1000);
        String origin = sourceUrl.replaceFirst(getEndpoint(sourceUrl), "") + "-"
                + timestamp + "-" + rand + "-0-" + sdkContext.getCdnKey();
        String md5 = Hashing.md5().hashString(origin, Charsets.UTF_8).toString().toLowerCase();
        return SdkCache.putUrl(sourceUrl, String.format("%s?auth_key=%s-%s-0-%s", sourceUrl, timestamp, rand, md5));
    }

}
